package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

type IniConfig struct {
	Mysql MysqlConfig `ini:"mysql"`
	Redis RedisConfig `ini:"redis"`
}

type MysqlConfig struct {
	Master   bool   `ini:"master"`
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	User     string `ini:"user"`
	Password string `ini:"password"`
}

type RedisConfig struct {
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	Password string `ini:"password"`
	Db       int    `ini:"db"`
}

func loadIni(filePath string, v interface{}) (err error) {
	// 传入的 接收值 v 必须为指针类型 因为需要对齐进行赋值操作
	t := reflect.TypeOf(v)
	if v == nil || t.Kind() != reflect.Ptr {
		return fmt.Errorf("接收者必须为指针类型\n")
	}

	el := t.Elem()
	if el.Kind() != reflect.Struct {
		return fmt.Errorf("接收者必须为一个结构体对象\n")
	}

	// 打开文件
	f, err := os.OpenFile(filePath, os.O_RDONLY, 0644)
	if err != nil {
		return err
	}

	// 读取整个文件
	b, _ := ioutil.ReadAll(f)
	// 释放文件占用
	_ = f.Close()

	// ini 文件中
	// ; 开头的为注释
	// [xxx] 为节
	// key=value 为键值对

	// 读取文件
	conf := string(b)
	// 替换换行符 \r\n 为 \n
	conf = strings.Replace(conf, "\r\n", "\n", -1)
	// 根据换行符 \n 分割行
	confSplit := strings.Split(conf, "\n")
	//fmt.Printf("%#v\n", confSplit)

	var curSection string
	for _, line := range confSplit {
		// 去除 首尾 空格符
		trimReg, _ := regexp.Compile("\\s")
		line = trimReg.ReplaceAllLiteralString(line, "")

		// 如果是以 ; 或 # 开头 的 视为注释 直接跳过
		annotationReg, _ := regexp.Compile("^[;|#]")
		if annotationReg.MatchString(line) {
			continue
		}

		sectionReg, _ := regexp.Compile("^\\[(.*)]$")
		if sectionReg.MatchString(line) {
			sectionMatch := sectionReg.FindAllStringSubmatch(line, 1)
			if len(sectionMatch) < 1 {
				return fmt.Errorf("无效的 节 %s\n", line)
			}
			if len(sectionMatch[0]) < 2 {
				return fmt.Errorf("无效的 节 %s\n", line)
			}

			section := sectionMatch[0][1]
			//fmt.Printf("节: %s\n", line)

			value := reflect.ValueOf(v).Elem()
			for i := 0; i < el.NumField(); i++ {
				field := el.Field(i)
				if field.Tag.Get("ini") == section {
					item := value.FieldByName(field.Name)
					if item.Kind() == reflect.Ptr {
						return fmt.Errorf("接收者 %s 不能为指针\n", section)
					}
					//if item.Kind() == reflect.Struct {
					//	fmt.Printf("%s 为结构体\n", section)
					//}

					curSection = field.Name
					break
				}
			}
			continue
		}

		if strings.EqualFold(curSection, "") {
			continue
		}

		keyValueReg, _ := regexp.Compile("^(.*)=(.*)$")
		if keyValueReg.MatchString(line) {
			keyValueMatch := keyValueReg.FindAllStringSubmatch(line, -1)
			if len(keyValueMatch) < 1 {
				//fmt.Println("未找到 匹配的 键值对")
				continue
			}
			keyValueSubMatch := keyValueMatch[0]
			if len(keyValueSubMatch) < 3 {
				//fmt.Println("未找到 匹配的 键值对")
				continue
			}
			k := keyValueSubMatch[1]
			val := keyValueSubMatch[2]
			//fmt.Printf("键值对: k: %#v => v: %#v\n", k, val)

			sectionReflectType, _ := reflect.TypeOf(v).Elem().FieldByName(curSection)
			sectionType := sectionReflectType.Type
			sectionVal := reflect.ValueOf(v).Elem().FieldByName(curSection)

			if sectionVal.Kind() != reflect.Struct {
				return fmt.Errorf("section 接收者 需为 struct 类型\n")
			}

			for i := 0; i < sectionVal.NumField(); i++ {
				selectionKey := sectionType.Field(i).Tag.Get("ini")

				if strings.EqualFold(selectionKey, k) {
					//fmt.Println(selectionKey, sectionVal.Field(i).Kind())
					switch sectionVal.Field(i).Kind() {
					case reflect.Int:
						intVal, err := strconv.Atoi(val)
						if err != nil {
							return err
						}
						sectionVal.Field(i).SetInt(int64(intVal))
					case reflect.String:
						sectionVal.Field(i).SetString(val)
					case reflect.Bool:
						boolVal, err := strconv.ParseBool(val)
						if err != nil {
							return err
						}
						sectionVal.Field(i).SetBool(boolVal)
					case reflect.Float64:
						floatVal, err := strconv.ParseFloat(val, 64)
						if err != nil {
							return err
						}
						sectionVal.Field(i).SetFloat(floatVal)
					}
					break
				}
			}

			continue
		}

		fmt.Println(line)
	}

	return
}

func main() {
	config := new(IniConfig)
	err := loadIni("./base/reflect/ini/conf.ini", config)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Printf("%#v\n", config)
}
